package com.ssb.util.sign;

/**
 * @author Karl
 * @since 2012-10-25
 * Base64要求把每三个8Bit的字节转换为四个6Bit的字节（3*8 = 4*6 = 24），
 * 然后把6Bit再添两位高位0，组成四个8Bit的字节，也就是说，转换后的字符串理论上将要比原来的长1/3。
 * 关于这个编码的规则：
　　①.把3个字符变成4个字符..
　　②每76个字符加一个换行符..
　　③.最后的结束符也要处理..
 */
public class Base64Helper {

    /**
     * 多少个 base64 字符作为一行
     */
    private static final int LINE_LENGTH = 76;
    private static char PAD = '=';
    /**
     * 每行最后添加的换行字符
     */
    private static final char[] LINE_BREAKS = new char[]{'\r', '\n'};
    private static final char[] STANDARD_ENCODE_TABLE = new char[]{
            'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O',
            'P','Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d',
            'e','f','g','h','i','j','k','l','m','n','o','p','q','r','s',
            't','u','v','w','x','y','z','0','1','2','3','4','5','6','7',
            '8','9','+','/'
    };
    /**
     * url 安全的 Base64 编码，用 -,_替换 +,/
     */
    private static final byte[] URL_SAFE_ENCODE_TABLE = {
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
            'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
            'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
    };

    /**
     *
     */
    private static final byte[] DECODE_TABLE = {
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, 62, -1, 62, -1, 63, 52, 53,
            54, 55, 56, 57, 58, 59, 60, 61, -1, -1,
            -1, -1, -1, -1, -1,  0,  1,  2,  3,  4,
            5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
            15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
            25, -1, -1, -1, -1, 63, -1, 26, 27, 28,
            29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
            39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
            49, 50, 51
    };
    /**
     * 将字节转换为 base64 编码
     * @param inData
     * @return
     */
    public static String encode(byte[] inData)
    {
        int caculateCharLength = inData.length * 4 / 3 + inData.length / LINE_LENGTH + 10;
        char[] outChars = new char[caculateCharLength];
        int charCount = encode(outChars, inData);
        return String.valueOf(outChars, 0, charCount);
    }
    /**
     * 将指定字节转换为字符数组输出
     * @param outChars
     * @param inData
     * @return
     */
    public static int encode(char[] outChars, byte[] inData)
    {
        return encode(outChars, inData, 0, inData.length, false);
    }
    /**
     * 将给定字节数组中的给定长度
     * @param outChars
     * @param inData
     * @param offset
     * @param length
     * @param insertLineBreaks
     * @return
     */
    private static int encode(char[] outChars, byte[] inData, int offset, int length, boolean insertLineBreaks) {
        //每3个 byte 进行一个处理，不足3个的单独处理。
        int lengthmod3 = length%3;
        //计算到哪个位置的时候说明已经基本处理完毕，只剩下不足3个的 byte 了
        int calcLength = offset + (length - lengthmod3);

        int outCharIndex=0;
        //统计编码后的 base64 字符个数，用来判断是否需要加入换行符。
        int charcount = 0;
        //Convert three bytes at a time to base64 notation.  This will consume 4 chars.
        int i;

        // get a pointer to the base64Table to avoid unnecessary range checking
        //fixed(char* base64 = base64Table)
        {
            for (i=offset; i<calcLength; i+=3)
            {
                //判断是否加入换行符，每 LINE_LENGTH(76) 个字符后面增加一个换行
                if (insertLineBreaks) {
                    if (charcount == LINE_LENGTH) {
                        outChars[outCharIndex++] = '\r';
                        outChars[outCharIndex++] = '\n';
                        charcount = 0;
                    }
                    charcount += 4;
                }
                //获取第一个字节前 6 个 bit，保存为一个字符。   0xfc=(11111100)
                outChars[outCharIndex] = STANDARD_ENCODE_TABLE[(inData[i]&0xfc)>>2];
                //获取第一个字节的后 2 个 bit，结合第二个字节的前 4 个 bit 0x03=00000011, 0xf0=11110000
                outChars[outCharIndex+1] = STANDARD_ENCODE_TABLE[((inData[i]&0x03)<<4) | ((inData[i+1]&0xf0)>>4)];
                //获取第二个字节的后 4 个 bit，结合第三个字节的前 2 个 bit 0x0f=00001111, 0xc0=11000000
                outChars[outCharIndex+2] = STANDARD_ENCODE_TABLE[((inData[i+1]&0x0f)<<2) | ((inData[i+2]&0xc0)>>6)];
                //获取第三个字节的后 6 个 bit 0x3f=00111111
                outChars[outCharIndex+3] = STANDARD_ENCODE_TABLE[(inData[i+2]&0x3f)];
                outCharIndex += 4;
            }

            //Where we left off before
            i =  calcLength;
            //如果已经满 76 字符，而且后续还有剩余字节需要处理，则增加换行字符
            if (insertLineBreaks && (lengthmod3 !=0) && (charcount == 76)) {
                outChars[outCharIndex++] = '\r';
                outChars[outCharIndex++] = '\n';
            }

            switch(lengthmod3)
            {
                case 2: //One character padding needed
                    outChars[outCharIndex] = STANDARD_ENCODE_TABLE[(inData[i]&0xfc)>>2];
                    outChars[outCharIndex+1] = STANDARD_ENCODE_TABLE[((inData[i]&0x03)<<4)|((inData[i+1]&0xf0)>>4)];
                    outChars[outCharIndex+2] = STANDARD_ENCODE_TABLE[(inData[i+1]&0x0f)<<2];
                    outChars[outCharIndex+3] = PAD; //Pad
                    outCharIndex+=4;
                    break;
                case 1: // Two character padding needed
                    outChars[outCharIndex] = STANDARD_ENCODE_TABLE[(inData[i]&0xfc)>>2];
                    outChars[outCharIndex+1] = STANDARD_ENCODE_TABLE[(inData[i]&0x03)<<4];
                    outChars[outCharIndex+2] = PAD; //Pad
                    outChars[outCharIndex+3] = PAD; //Pad
                    outCharIndex+=4;
                    break;
            }
        }

        return outCharIndex;
    }

    public static byte[] decode(String base64String)
    {
        return decode(base64String.toCharArray());
    }
    /**
     *
     * @param inChars
     * @return
     */
    public static byte[] decode(char[] inChars)
    {
        //base64 编码的字符,最后的解码结果不会超出 字符总长度/4*3
        //因为有换行和最后的 Pad，如果都没有的情况下才是正好  字符总长度/4*3
        int capicity = inChars.length/4*3;
        byte[] buffer = new byte[capicity];
        int length = decode(inChars, buffer);
        byte[] result = new byte[length];
        for(int i=0;i<length;i++)
        {
            result[i] = buffer[i];
        }
        //byte[] result = Arrays.copyOfRange(buffer, 0, length);
        return result;
    }
    /**
     * 将给定 base64 字符串中的数据解码为对应的 byte 数组。
     * 返回解码后的数组长度。
     * @param inChars
     * @param outData
     * @return
     */
    public static int decode(char[] inChars, byte[] outData) {
        int charIndex = 0;
        int byteIndex = 0;
        while(true)
        {
            //如果已经到字符末尾，则跳出循环
            if(charIndex >= inChars.length)
                break;

            //如果遇到一个非 base64 字符，则获取下一个字符。
            //应该是换行, \r\n
            if(DECODE_TABLE[inChars[charIndex]] == -1)
            {
                charIndex++;
                continue;
            }

            //对正常的 4 个字符一组进行处理
            byte first = DECODE_TABLE[inChars[charIndex]];
            byte second = DECODE_TABLE[inChars[charIndex+1]];
            byte third = DECODE_TABLE[inChars[charIndex+2]];
            byte fourth = DECODE_TABLE[inChars[charIndex+3]];

            if(inChars[charIndex+2] == PAD)
            {
                //有两个 PAD，说明结果只有一个有效字节
                //获取第一个字节的 3-8 位作为新数据的前 1-6 位，用第二个字节的 3-4 位作为新字节的 7-8位
                //byte firstR = (byte)(((first << 2) & 0xfc) | ((second >> 4) & 0x03));
                byte firstR = (byte)((first << 2) | (second >> 4));
                outData[byteIndex++] = firstR;
            }
            else if(inChars[charIndex+3] == PAD)
            {
                //有一个 PAD，说明结果只有两个有效字节
                //获取第一个字节的 3-8 位作为新数据的前 1-6 位，用第二个字节的 3-4 位作为新字节的 7-8位
                //byte firstR = (byte)(((first << 2) & 0xfc) | ((second >> 4) & 0x03));
                byte firstR = (byte)((first << 2) | (second >> 4));
                //获取第二个字节的 5-8 位作为新数据的前 1-4 位，用第三个字节的 3-6 位作为新字节的 5-8位
                //byte secondR = (byte)(((second << 4) & 0xf0) | ((third >> 2) & 0x0f));
                byte secondR = (byte)((second << 4) | (third >> 2));
                outData[byteIndex++] = firstR;
                outData[byteIndex++] = secondR;
            }
            //都是有效字节
            else
            {
                //获取第一个字节的 3-8 位作为新数据的前 1-6 位，用第二个字节的 3-4 位作为新字节的 7-8位
                //byte firstR = (byte)(((first << 2) & 0xfc) | ((second >> 4) & 0x03));
                byte firstR = (byte)((first << 2) | (second >> 4));
                //获取第二个字节的 5-8 位作为新数据的前 1-4 位，用第三个字节的 3-6 位作为新字节的 5-8位
                //byte secondR = (byte)(((second << 4) & 0xf0) | ((third >> 2) & 0x0f));
                byte secondR = (byte)((second << 4) | (third >> 2));
                //获取第三个字节的 7-8 位作为新数据的前 1-2 位，用第三个字节的 3-8 位作为新字节的 3-8位
                //byte thirdR = (byte)(((third << 6) & 0xc0) | (fourth & 0xcf));
                byte thirdR = (byte)((third << 6) | fourth);
                outData[byteIndex++] = firstR;
                outData[byteIndex++] = secondR;
                outData[byteIndex++] = thirdR;
            }
            //最后的处理
            charIndex+=4;
            //对于包含附加字符的处理，附加字符为 =
        }
        //返回实际解码后的  byte 的字节数
        return byteIndex;
    }
    /**
     * 将从指定位置开始的 base64 字符转换为对应的 byte
     * @param toDecodeChar 要转换的 base64 字符数组
     * @param charStart 从数组中的哪个位置开始转换
     * @param destBytes 保存到 byte 数组中的位置
     * @param byteStart 从 byte 数组中的哪个位置开始
     */
    private static int CharsToBytes(char[] toDecodeChar, int charStart, byte[] destBytes, int byteStart)
    {
        //对 4 个字符进行处理
        byte first = DECODE_TABLE[toDecodeChar[charStart++]];
        byte second = DECODE_TABLE[toDecodeChar[charStart++]];
        byte third = DECODE_TABLE[toDecodeChar[charStart++]];
        byte fourth = DECODE_TABLE[toDecodeChar[charStart++]];

        //获取第一个字节的 3-8 位作为新数据的前 1-6 位，用第二个字节的 3-4 位作为新字节的 7-8位
        byte firstR = (byte)(((first << 2) & 0xfc) | ((second >> 4) & 0x03));
        //获取第二个字节的 5-8 位作为新数据的前 1-4 位，用第三个字节的 3-6 位作为新字节的 5-8位
        byte secondR = (byte)(((second << 4) & 0xf0) | ((third >> 2) & 0x0f));
        //获取第三个字节的 7-8 位作为新数据的前 1-2 位，用第三个字节的 3-8 位作为新字节的 3-8位
        byte thirdR = (byte)(((third << 6) & 0xc0) | (fourth & 0xcf));
        destBytes[byteStart++] = firstR;
        destBytes[byteStart++] = secondR;
        destBytes[byteStart++] = thirdR;
        return charStart;
    }
}

